﻿using System;
using System.Linq;
using System.Collections.Generic;
using Inovout.Caching;
using System.Reflection;

namespace Inovout.Descriptors
{
    public class TypeDescriptor : MemberDescriptor
    {
        #region Field
        private static DictionaryCache<Type, TypeDescriptor> typeDescriptorCache = new DictionaryCache<Type, TypeDescriptor>();
        private TypeDescriptor baseTypeDescriptor;

        #endregion

        protected Type ReflectionType
        {
            get { return base.MemberInfo as Type; }
        }

        #region Constructor


        /// <summary>
        /// 初始化 EnumDictionary类的新实例。
        /// </summary>
        /// <param name="type">类型</param>
        public TypeDescriptor(Type type)
            : base(type)
        {
            ArgumentAssert.IsNotNull(type, "type");

            fields = type.GetFields(AllBindingFlags)
                    .Select(fieldInfo => CreateFieldDescriptor(fieldInfo)).ToArray();
            constructors = type.GetConstructors()
                .Select(constructorInfo => CreateConstructorDescriptor(constructorInfo)).ToArray();
            properties = type.GetProperties(AllBindingFlags)
                    .Select(propertyInfo => CreatePropertyDescriptor(propertyInfo)).ToArray();

            if (type.BaseType != null)
            {
                baseTypeDescriptor = TypeDescriptor.Get(type.BaseType);
            }
        }

        #endregion

        protected virtual ConstructorDescriptor CreateConstructorDescriptor(ConstructorInfo constructorInfo)
        {
            return new ConstructorDescriptor(constructorInfo);
        }

        protected virtual PropertyDescriptor CreatePropertyDescriptor(PropertyInfo propertyInfo)
        {
            return new PropertyDescriptor(propertyInfo);
        }

        protected virtual FieldDescriptor CreateFieldDescriptor(FieldInfo fieldInfo)
        {
            return new FieldDescriptor(fieldInfo);
        }

        private FieldDescriptor[] fields;
        public FieldDescriptor[] Fields
        {
            get { return fields; }
        }

        public PropertyDescriptor GetProperty(string propertyName)
        {
            if (properties.Any(property => property.Name.Equals(propertyName)))
            {
                return properties.Single(property => property.Name.Equals(propertyName));
            }
            else
            {
                return baseTypeDescriptor.GetProperty(propertyName);
            }

        }


        private PropertyDescriptor[] properties;
        public PropertyDescriptor[] Properties
        {
            get { return properties; }
        }

        private ConstructorDescriptor[] constructors;
        public ConstructorDescriptor[] Constructors
        {
            get { return constructors; }
        }
        public static TypeDescriptor Get<TType>() where TType : class
        {
            return Get(typeof(TType));
        }

        public static TypeDescriptor Get(Type type)
        {
            ArgumentAssert.IsNotNull(type, "type");
            if (type.IsEnum)
            {
                return typeDescriptorCache.AddOrGet(type, () => new EnumDescriptor(type));
            }
            else
            {
                return typeDescriptorCache.AddOrGet(type, () => new TypeDescriptor(type));
            }
        }

        public FieldDescriptor GetField(string fieldName)
        {
            if (fields.Any(field => field.Name.Equals(fieldName)))
            {
                return fields.Single(field => field.Name.Equals(fieldName));
            }
            else
            {
                return baseTypeDescriptor.GetField(fieldName);
            }
        }

        public bool HasProperty(string propertyName)
        {
            return GetProperty(propertyName) != null;
        }

        public bool HasField(string fieldName)
        {
            return GetField(fieldName) != null;
        }

        public Dictionary<string, object> GetPropertyValues(object instance)
        {
            ArgumentAssert.IsNotNull(instance, "instance");
            return properties.ToDictionary(property => property.Name, property => property.GetValue(instance));
        }
        public void SetPropertyValues(object instance, IDictionary<string, object> propertyValues)
        {
            ArgumentAssert.IsNotNull(instance, "instance");
            ArgumentAssert.IsNotNull(propertyValues, "propertyValues");
            foreach (PropertyDescriptor propertyDesciptor in properties.Where(property => !property.IsReadOnly))
            {
                object value;
                if (propertyValues.TryGetValue(propertyDesciptor.Name, out value))
                {
                    propertyDesciptor.SetValue(instance, value);
                }
            }

        }

    }
}
